package com.sxhuayuan.parking.plugin.parking;

import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.Assert;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sxhuayuan.parking.compenent.Message;
import com.sxhuayuan.parking.compenent.web.RespMessage;
import com.sxhuayuan.parking.config.Platform;
import com.sxhuayuan.parking.dto.CreateMcardChargeOrderDTO;
import com.sxhuayuan.parking.dto.ParkFeeCacheBean;
import com.sxhuayuan.parking.entity.Member;
import com.sxhuayuan.parking.entity.MemberCarPlate;
import com.sxhuayuan.parking.entity.ParkingLot;
import com.sxhuayuan.parking.exception.MyException;
import com.sxhuayuan.parking.exception.NettyException;
import com.sxhuayuan.parking.plugin.parking.bean.McardChargeOption;
import com.sxhuayuan.parking.plugin.parking.result.McardAddResultWrap;
import com.sxhuayuan.parking.plugin.parking.result.McardCancelResultWrap;
import com.sxhuayuan.parking.plugin.parking.result.McardChargeOrderCreateResultWrap;
import com.sxhuayuan.parking.plugin.parking.result.McardQueryChargeOptionsResultWrap;
import com.sxhuayuan.parking.plugin.parking.result.McardQueryResultWrap;
import com.sxhuayuan.parking.plugin.parking.result.ParkingLotInfoQueryResultWrap;
import com.sxhuayuan.parking.plugin.parking.result.QueryParkingFeeResultWrap;
import com.sxhuayuan.parking.queue.msg.NotifyParkingLotMsg;
import com.sxhuayuan.parking.service.OriginalParkingInfoService;
import com.sxhuayuan.parking.service.ParkFeeCacheService;
import com.sxhuayuan.parking.service.RedisService;
import com.sxhuayuan.parking.socket.ChannelGroupPool;
import com.sxhuayuan.parking.socket.bo.ParkingLotInterfaceRequest;
import com.sxhuayuan.parking.utils.SettingUtils;

public abstract class ParkingPlugin implements Comparable<ParkingPlugin>, InitializingBean, DisposableBean {

	private static Logger log = LoggerFactory.getLogger(ParkingPlugin.class);

	private static CloseableHttpClient httpClient;

	@Resource(name = "taskExecutor")
	ThreadPoolTaskExecutor executor;
	@Resource(name = "originalParkingInfoServiceImpl")
	OriginalParkingInfoService opinfoService;
	@Resource(name = "redisServiceImpl")
	RedisService redisService;
	@Autowired
	ParkFeeCacheService parkFeeCacheService;
	@Autowired
	ChannelGroupPool channelGroup;

	/** 车场主动通知类型 */
	public enum NoticeType {
		/** 入场后通知 */
		afterin,
		/** 出场前通知 */
		beforeout,
		/** 出场后通知 */
		afterout
	}

	private static final Map<String, String> HEADERS_JSON = new HashMap<>();

	private static Map<String, Map<String, McardChargeOption>> monthCardChargeOptionMap = new HashMap<>();

	/**
	 * connectionRequestTimeout: 从连接池中获取连接的超时时间，超过该时间未拿到可用连接<br>
	 * connectTimeout: 连接上服务器(握手成功)的时间，超出该时间抛出connect timeout <br>
	 * socketTimeout: 服务器返回数据(response)的时间，超过该时间抛出read timeout<br>
	 */
	static {
		List<Header> defaultHeaders = new ArrayList<>();
		defaultHeaders.add(new BasicHeader("accept", "*/*"));
		defaultHeaders.add(new BasicHeader("connection", "Keep-Alive"));
		// 连接最多等待6秒，无响应则断开
		RequestConfig config = RequestConfig.custom().setConnectTimeout(6000).setSocketTimeout(6000).build();
		httpClient = HttpClients.custom().setDefaultRequestConfig(config).setConnectionTimeToLive(300, TimeUnit.SECONDS).setDefaultHeaders(defaultHeaders).build();
		HEADERS_JSON.put("Content-Type", "application/json;charset=utf-8");
	}

	/**
	 * 获取请求编码方式
	 * 
	 * @return
	 */
	public String getCharset() {
		return "utf-8";
	}

	/**
	 * 是否支持出入场事件实时通知本系统
	 * 
	 * @return
	 */
	public boolean isSupportEventPush() {
		return false;
	}

	/**
	 * 是否支持本系统异步通知订单结果
	 * 
	 * @return
	 */
	public boolean isSupportOrderNotify() {
		return false;
	}

	/** debug模式下是否开启回调车场，谨慎开启，避免测试数据发送到车场生产环境 */
	public boolean isOpenNotifyWhileDebug() {
		return false;
	}

	/**
	 * 获取插件ID
	 * 
	 * @return
	 */
	public abstract String getId();

	/**
	 * 获取插件名称
	 * 
	 * @return
	 */
	public abstract String getName();

	// /**
	// * 转换Resp为数据库entity对象
	// *
	// * @param t
	// * @return
	// */
	// @Deprecated
	// protected abstract OriginalParkingInfo parseEntity(String lotSn, String
	// resp);

	/**
	 * 转换resp为停车记录并返回停车信息
	 * 
	 * @param lotSn
	 * @param carPlate TODO
	 * @param resp
	 * @return
	 */
	public abstract ParkFeeCacheBean saveParkInRecord(String lotSn, String carPlate, String resp);

	/**
	 * 调用第3方接口查询停车信息并生成车场的缴费订单，一般会停止计费并生成订单，15分钟（根据车场配置）之内如果没有缴费则继续计费<br>
	 * 
	 * @param lot
	 * @param carPlate
	 * @return
	 */
	public abstract QueryParkingFeeResultWrap queryParkingFee(ParkingLot lot, String carPlate);

	/**
	 * 根据出口的设备id查询当前出口准备离场的车辆的缴费信息
	 * 
	 * @param lot
	 * @param carPlate
	 * @return
	 */
	public abstract QueryParkingFeeResultWrap queryLeavingCarParkingLotOrder(ParkingLot lot, String exitSn, Member member);

	/**
	 * 通知第三方缴费成功
	 * 
	 * @param order
	 * @param lotSn
	 * @param carPlate
	 * @return
	 */
	// @CacheEvict(value = QueryParkingFeeResultWrap.CACHE_NAME, key = "#lotSn + '_'
	// + #carPlate")
	public abstract boolean notifyParkingFeeOrderSuccess(NotifyParkingLotMsg msg);

	/**
	 * 通知车场月卡订单缴费成功
	 * 
	 * @param order
	 * @param lotSn
	 * @param carPlate
	 * @return
	 */
	public abstract boolean notifyMonthCardChargeOrderSuccess(String order, String lotSn, String carPlate);

	/**
	 * 查询车辆月卡信息
	 * 
	 * @param lotSn
	 * @param carPlate
	 * @return
	 */
	@Cacheable(value = McardQueryResultWrap.CACHE_NAME, unless = "#result.isSuccess == false")
	public abstract McardQueryResultWrap monthCardQuery(String lotSn, MemberCarPlate carPlate);

	/**
	 * 月卡充值结果预览
	 * 
	 * @param lotSn
	 * @param carPlate
	 * @param month
	 * @return
	 */
	public abstract McardQueryResultWrap monthCardChargePreview(String lotSn, String carPlate, Integer month);

	/**
	 * 查询车场信息
	 * 
	 * @param lotSn
	 * @return
	 */
	public abstract ParkingLotInfoQueryResultWrap parkingLotInfoQuery(String lotSn);

	/**
	 * 查询月卡充值选项
	 * 
	 * @param lotSn
	 * @return
	 */
	@Cacheable(value = McardQueryChargeOptionsResultWrap.CACHE_NAME, unless = "#result.isSuccess == false")
	public abstract McardQueryChargeOptionsResultWrap monthCardQueryChargeOptions(String lotSn);

	/**
	 * 新用户添加月卡（不是必须，看接口方要求。车安需要）
	 * 
	 * @return
	 */
	public McardAddResultWrap monthCardAdd(String lotSn, String carPlate) {
		return null;
	}

	/**
	 * 创建月卡缴费订单
	 * 
	 * @param memberCarPlate
	 * @param dto
	 * @return
	 */
	public abstract McardChargeOrderCreateResultWrap monthCardCreateOrder(MemberCarPlate memberCarPlate, CreateMcardChargeOrderDTO dto, Platform platform);

	/**
	 * 注销月卡
	 * 
	 * @param lotSn
	 * @param carPlate
	 * @return
	 */
	public abstract McardCancelResultWrap monthCardCancel(String lotSn, String carPlate);

	public abstract String execute(String api, String lotSn, Map map);

	/**
	 * 入场后通知
	 * 
	 * @param request
	 *            request
	 * @param response
	 *            response
	 * @param parkingLot
	 *            车场
	 */
	public abstract void afterInNoticeHanler(HttpServletRequest request, HttpServletResponse response, ParkingLot parkingLot);

	/**
	 * 出场前通知
	 * 
	 * @param request
	 *            request
	 * @param response
	 *            response
	 * @param parkingLot
	 *            车场
	 */
	public abstract void afterOutNoticeHanler(HttpServletRequest request, HttpServletResponse response, ParkingLot parkingLot);

	/**
	 * 出场后通知处理f
	 * 
	 * @param request
	 * @param response
	 * @param parkingLot
	 */
	public abstract void beforeOutNoticeHandler(HttpServletRequest request, HttpServletResponse response, ParkingLot parkingLot);

	// =================================以上是需要实现的方法==================================

	/**
	 * 后台线程自动持久化缴费原始订单
	 * 
	 * @param lotSn
	 * @param resp
	 */
	protected Long submitUpdateOriginalInfoTask(String lotSn, String carPlate, Float money, String resp) {
		Future<Long> future = executor.submit(new Callable<Long>() {
			@Override
			public Long call() throws Exception {
				try {
					if (money != null && money > 0) {
						log.debug("保存停车信息到数据库...");
						ParkFeeCacheBean parkFeeCache = saveParkInRecord(lotSn, carPlate, resp);
						if (parkFeeCache != null) {
							log.debug("缓存费用信息...");
							parkFeeCacheService.put(parkFeeCache);
							return parkFeeCache.getUniqueId();
						}
					} else {
						log.info("费用为0，不保存入场");
					}
				} catch (Exception e) {
					log.error("", e);
				}
				return null;
			}
		});
		try {
			return future.get();
		} catch (InterruptedException | ExecutionException e) {
			log.error("", e);
		}
		return null;
	}

	/**
	 * 获取
	 * 
	 * @param lotSn
	 * @return
	 */
	public final Map<String, McardChargeOption> getMonthCardChargeOptionsMap(String lotSn) {
		Map<String, McardChargeOption> map = monthCardChargeOptionMap.get(lotSn);
		if (map == null) {
			synchronized (monthCardChargeOptionMap) {
				log.debug("{}的monthCardChargeOption为空，初始化...", lotSn);
				McardQueryChargeOptionsResultWrap rs = monthCardQueryChargeOptions(lotSn);
				if (rs.getIsSuccess()) {
					List<McardChargeOption> options = rs.getOptions();
					map = new HashMap<>();
					for (McardChargeOption o : options) {
						map.put(o.getTariffId(), o);
					}
					monthCardChargeOptionMap.put(lotSn, map);
				}
			}
		}
		return map;
	}

	/**
	 * 计算月卡缴费单应收金额
	 * 
	 * @param carType
	 * @param month
	 * @return
	 */
	public BigDecimal calcMonthCardOrderReceivableFee(McardChargeOption opt, Integer month) {
		return SettingUtils.get().setScale(new BigDecimal(opt.getCharge() * month));
	}

	/**
	 * 将发送post请求
	 * 
	 * @param url
	 * @param req
	 * @param cls
	 * @return
	 */
	public final <T> T postJson(String lotSn, String url, AbstractRequest req, Class<T> cls, boolean isTcp) {
		String post = post(lotSn, url, req.toJSON(), HEADERS_JSON, isTcp);
		if (post != null)
			return JSON.parseObject(post, cls);
		return null;
	}

	public final String postJson(String lotSn, String url, String json, boolean isTcp) {
		return post(lotSn, url, json, HEADERS_JSON, isTcp);
	}

	public final String postJson(String lotSn, String url, String json, Map<String, String> header, boolean isTcp) {
		return post(lotSn, url, json, header, isTcp);
	}

	/**
	 * POST请求，6秒超时
	 * 
	 * @param url
	 *            URL
	 * @param parameterMap
	 *            请求参数
	 * @return 返回结果
	 */
	protected String post(String url, Map<String, Object> parameterMap) {
		Assert.hasText(url, "");
		String result = null;
		CloseableHttpResponse httpResponse = null;
		try {
			HttpPost httpPost = new HttpPost(url);
			List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
			if (parameterMap != null) {
				for (Entry<String, Object> entry : parameterMap.entrySet()) {
					String name = entry.getKey();
					String value = ConvertUtils.convert(entry.getValue());
					if (StringUtils.isNotEmpty(name)) {
						nameValuePairs.add(new BasicNameValuePair(name, value));
					}
				}
			}
			httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, getCharset()));
			httpResponse = httpClient.execute(httpPost);
			if (httpResponse == null) {
				throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
			}
			HttpEntity httpEntity = httpResponse.getEntity();
			result = EntityUtils.toString(httpEntity);
			EntityUtils.consume(httpEntity);
		} catch (IOException e) {
			log.error("", e);
			throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
		} catch (Exception e) {
			log.error("", e);
		} finally {
			try {
				if (httpResponse != null) {
					httpResponse.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return result;
	}

	/**
	 * 模拟发送复杂表单，6秒超时
	 *
	 * @author gaoyan
	 * @param url
	 * @param multipartEntityBuilder
	 * @return
	 * @since JDK 1.7
	 */
	protected String postForm(String url, MultipartEntityBuilder multipartEntityBuilder) {
		CloseableHttpResponse httpResponse = null;
		String respText = null;
		try {
			HttpPost httpPost = new HttpPost(url);
			httpPost.setEntity(multipartEntityBuilder.build());
			httpResponse = httpClient.execute(httpPost);
			if (httpResponse == null) {
				throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
			}
			HttpEntity httpEntity = httpResponse.getEntity();
			respText = EntityUtils.toString(httpEntity);
			EntityUtils.consume(httpEntity);
		} catch (IOException e) {
			log.error("", e);
			throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
		} catch (Exception e) {
			log.error("", e);
		} finally {
			try {
				if (httpResponse != null) {
					httpResponse.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return respText;
	}

	/**
	 * GET请求，6秒超时
	 * 
	 * @param url
	 *            URL
	 * @param parameterMap
	 *            请求参数
	 * @return 返回结果
	 */
	protected String get(String url, Map<String, Object> parameterMap) {
		Assert.hasText(url, "");
		String result = null;
		CloseableHttpResponse httpResponse = null;
		try {
			List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
			if (parameterMap != null) {
				for (Entry<String, Object> entry : parameterMap.entrySet()) {
					String name = entry.getKey();
					String value = ConvertUtils.convert(entry.getValue());
					if (StringUtils.isNotEmpty(name)) {
						nameValuePairs.add(new BasicNameValuePair(name, value));
					}
				}
			}
			HttpGet httpGet = new HttpGet(url + (StringUtils.contains(url, "?") ? "&" : "?") + EntityUtils.toString(new UrlEncodedFormEntity(nameValuePairs, getCharset())));
			httpResponse = httpClient.execute(httpGet);
			if (httpResponse == null) {
				throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
			}
			HttpEntity httpEntity = httpResponse.getEntity();
			result = EntityUtils.toString(httpEntity);
			EntityUtils.consume(httpEntity);
		} catch (IOException e) {
			log.error("", e);
			throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
		} catch (Exception e) {
			log.error("", e);
		} finally {
			try {
				if (httpResponse != null) {
					httpResponse.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return result;
	}

	/**
	 * 请求车场接口，通过http或者websocket
	 * 
	 * @param lotSn
	 * @param url
	 * @param request
	 * @param headers
	 * @param isTcp
	 * @return
	 */
	protected String post(String lotSn, String url, String request, Map<String, String> headers, boolean isTcp) {
		if (isTcp) {
			int index = url.indexOf("//");
			if (index != -1) {
				url = url.substring(index + 2);
			}
			String uri = url.substring(url.indexOf("/"));
			ParkingLotInterfaceRequest req = new ParkingLotInterfaceRequest(uri, headers, request);
			try {
				return channelGroup.sendMsgSync(lotSn, req, 6 * 1000);
			} catch (NettyException e) {
				throw new MyException(Message.COMMON_SERVICE_NOT_AVAILABLE);
			}
		} else {
			return post(url, request, headers);
		}
	}

	/**
	 * 发送post请求，6秒超时
	 * 
	 * @param url
	 * @param request
	 * @param headers
	 * @param isTcp是否websocket请求接口
	 * @return
	 */
	protected String post(String url, String request, Map<String, String> headers) {
		Assert.hasText(url, "");
		String result = null;
		CloseableHttpResponse httpResponse = null;
		try {
			HttpPost httpPost = new HttpPost(url);
			String sequese = RandomStringUtils.randomAlphabetic(8);
			if (headers != null) {
				for (Map.Entry<String, String> e : headers.entrySet()) {
					log.debug("[{}]header: {}={}", sequese, e.getKey(), e.getValue());
					httpPost.addHeader(e.getKey(), e.getValue());
				}
			}
			log.debug("[{}]url={}, request={}", sequese, url, request);
			StringEntity entity = new StringEntity(request, Charset.forName("utf-8"));
			httpPost.setEntity(entity);
			httpResponse = httpClient.execute(httpPost);
			if (httpResponse == null) {
				throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
			}
			HttpEntity httpEntity = httpResponse.getEntity();
			result = EntityUtils.toString(httpEntity);
			log.debug("[{}]response={}", sequese, result);
			// EntityUtils.consume(httpEntity);
		} catch (IOException e) {
			throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
		} catch (Exception e) {
			e.printStackTrace();
			log.error("", e);
		} finally {
			try {
				if (httpResponse != null)
					httpResponse.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return result;
	}

	/**
	 * 发送post请求，6秒超时 附带请求头签名验证
	 * 
	 * @param url
	 * @param request
	 * @param headers
	 * @param sign
	 *            签名
	 * @return
	 */
	protected String post(String url, String request, Map<String, String> headers, String sign) {
		Assert.hasText(url, "");
		String result = null;
		CloseableHttpResponse httpResponse = null;
		try {
			HttpPost httpPost = new HttpPost(url);
			String sequese = RandomStringUtils.randomAlphabetic(8);
			if (headers != null) {
				for (Map.Entry<String, String> e : headers.entrySet()) {
					log.debug("[{}]header: {}={}", sequese, e.getKey(), e.getValue());
					httpPost.addHeader(e.getKey(), e.getValue());
				}
			}
			httpPost.setHeader("sign", sign);
			log.debug("[{}]url={}, request={}", sequese, url, request);
			StringEntity entity = new StringEntity(request, Charset.forName("utf-8"));
			httpPost.setEntity(entity);
			httpResponse = httpClient.execute(httpPost);
			if (httpResponse == null) {
				throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
			}
			HttpEntity httpEntity = httpResponse.getEntity();
			result = EntityUtils.toString(httpEntity);
			log.debug("[{}]response={}", sequese, result);
			EntityUtils.consume(httpEntity);
		} catch (IOException e) {
			throw new MyException(RespMessage.Status.ERROR, Message.COMMON_SERVER_CONNECTION_FAILURE);
		} catch (Exception e) {
			log.error("", e);
		} finally {
			try {
				if (httpResponse != null)
					httpResponse.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		if (this == obj) {
			return true;
		}
		ParkingPlugin other = (ParkingPlugin) obj;
		return new EqualsBuilder().append(getId(), other.getId()).isEquals();
	}

	@Override
	public int hashCode() {
		return new HashCodeBuilder(17, 37).append(getId()).toHashCode();
	}

	public int compareTo(ParkingPlugin parkingPlugin) {
		return new CompareToBuilder().append(getId(), parkingPlugin.getId()).toComparison();
	}

	public JSONObject parseJSON(String s) {
		return JSONObject.parseObject(s);
	}

	@Override
	public void afterPropertiesSet() throws Exception {

	}

	@Override
	public void destroy() throws Exception {
		try {
			httpClient.close();
		} catch (Exception e) {
			log.error("", e);
		}
	}

	/** 车场推送通知地址 */
	protected String getNoticeUrl() {
		return SettingUtils.get().getSiteUrl() + "/common/parking/notice";
	}
}
